﻿using System;
using System.Collections.Generic;
using System.Text;

namespace TLang
{
    using Values;
    using Values.Primitives;

    public class Scope
    {
        private readonly Map<String, Map<String, Object>> table = new Map<string, Map<string, object>>();
        public Scope parent;
        
        public Scope()
        {
            this.parent = null;
        }
        
        public Scope(Scope parent)
        {
            this.parent = parent;
        }
        
        public Scope Copy()
        {
            Scope ret = new Scope();
            foreach (String name in table.Keys)
            {
                Map<String, Object> props = new Map<string, object>();
                props.PutAll(table.Get(name));
                ret.table.Put(name, props);
            }
            return ret;
        }
        
        public void PutAll(Scope other)
        {
            foreach (String name in other.table.Keys)
            {
                Map<String, Object> props = new Map<string, object>();
                props.PutAll(other.table.Get(name));
                table.Put(name, props);
            }
        }
        
        public Value Lookup(String name)
        {
            string[] items = name.Split(Constants.Atribute_Access_Chars);
            if (items.Length == 2)
            {
                RecordType record = Lookup(items[0]) as RecordType;
                if (record == null)
                {
                    Util.Abort("value is not a Value, shouldn't happen: " + name);
                    return null;
                }
                return record.properties.Lookup(items[1]);
            }
            else if (items.Length == 1)
            {
                Object v = LookupProperty(name, "value");
                if (v == null)
                {
                    return null;
                }
                else if (v is Value)
                {
                    return (Value)v;
                }
                else
                {
                    Util.Abort("value is not a Value, shouldn't happen: " + v);
                    return null;
                }
            }
            else
            {
                Util.Abort("value is not a Value, shouldn't happen: " + name);
                return null;
            }
        }
        
        public Value LookupLocal(String name)
        {
            Object v = LookupPropertyLocal(name, "value");
            if (v == null)
            {
                return null;
            }
            else if (v is Value) {
                return (Value)v;
            } else {
                Util.Abort("value is not a Value, shouldn't happen: " + v);
                return null;
            }
        }
        
        public Value LookupType(String name)
        {
            Object v = LookupProperty(name, "type");
            if (v == null)
            {
                return null;
            }
            else if (v is Value) {
                return (Value)v;
            } else {
                Util.Abort("value is not a Value, shouldn't happen: " + v);
                return null;
            }
        }
        
        public Value LookupLocalType(String name)
        {
            Object v = LookupPropertyLocal(name, "type");
            if (v == null)
            {
                return null;
            }
            else if (v is Value) {
                return (Value)v;
            } else {
                Util.Abort("value is not a Value, shouldn't happen: " + v);
                return null;
            }
        }
        
        public Object LookupPropertyLocal(String name, String key)
        {
            Map<String, Object> item = table.Get(name);
            if (item != null)
            {
                return item.Get(key);
            }
            else
            {
                return null;
            }
        }
        
        public Object LookupProperty(String name, String key)
        {
            Object v = LookupPropertyLocal(name, key);
            if (v != null)
            {
                return v;
            }
            else if (parent != null)
            {
                return parent.LookupProperty(name, key);
            }
            else
            {
                return null;
            }
        }
        
        public Map<String, Object> LookupAllProps(String name)
        {
            return table.Get(name);
        }
        
        public Scope FindDefiningScope(String name)
        {
            Object v = table.Get(name);
            if (v != null)
            {
                return this;
            }
            else if (parent != null)
            {
                return parent.FindDefiningScope(name);
            }
            else
            {
                return null;
            }
        }
        
        public static Scope BuildInitScope()
        {
            Scope init = new Scope();

            init.PutValue("+", new Add());
            init.PutValue("-", new Sub());
            init.PutValue("*", new Mult());
            init.PutValue("/", new Div());

            init.PutValue("<", new Lt());
            init.PutValue("<=", new LtE());
            init.PutValue(">", new Gt());
            init.PutValue(">=", new GtE());
            init.PutValue("=", new Eq());
            init.PutValue("and", new And());
            init.PutValue("or", new Or());
            init.PutValue("not", new Not());

            init.PutValue("print", new Print());

            init.PutValue("true", new BoolValue(true));
            init.PutValue("false", new BoolValue(false));

            init.PutValue("Int", Type.INT);
            init.PutValue("Bool", Type.BOOL);
            init.PutValue("String", Type.STRING);

            return init;
        }

        public static Scope BuildInitTypeScope()
        {
            Scope init = new Scope();

            init.PutValue("+", new Add());
            init.PutValue("-", new Sub());
            init.PutValue("*", new Mult());
            init.PutValue("/", new Div());

            init.PutValue("<", new Lt());
            init.PutValue("<=", new LtE());
            init.PutValue(">", new Gt());
            init.PutValue(">=", new GtE());
            init.PutValue("=", new Eq());
            init.PutValue("and", new And());
            init.PutValue("or", new Or());
            init.PutValue("not", new Not());
            init.PutValue("U", new U());

            init.PutValue("true", Type.BOOL);
            init.PutValue("false", Type.BOOL);

            init.PutValue("Int", Type.INT);
            init.PutValue("Bool", Type.BOOL);
            init.PutValue("String", Type.STRING);
            init.PutValue("Any", Value.ANY);

            return init;
        }
        
        public void Put(String name, String key, Object value)
        {
            Map<String, Object> item = table.Get(name);
            if (item == null)
            {
                item = new Map<string, object>();
            }
            item.Put(key, value);
            table.Put(name, item);
        }
        
        public void PutProperties(String name, Map<String, Object> props)
        {
            Map<String, Object> item = table.Get(name);
            if (item == null)
            {
                item = new Map<string, object>();
            }
            item.PutAll(props);
            table.Put(name, item);
        }
        
        public void PutValue(String name, Value value)
        {
            Put(name, "value", value);
        }
        
        public void PutType(String name, Value value)
        {
            Put(name, "type", value);
        }

        public ICollection<string> Keys
        {
            get { return table.Keys; }
        }
        
        public bool ContainsKey(String key)
        {
            return table.ContainsKey(key);
        }
        
        public override String ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (String name in table.Keys)
            {
                sb.Append(Constants.SQUARE_BEGIN).Append(name).Append(" ");
                foreach (var e in table.Get(name))
                {
                    sb.Append(":" + e.Key + " " + e.Value);
                }
                sb.Append(Constants.SQUARE_END);
            }
            return sb.ToString();
        }
    }
}
